import EventEmitter from 'events';
import { remote, shell } from 'electron';
import path from 'path';
import { spawn } from 'child_process';
import fs from 'fs';
import { promisify } from 'util';
import jpeg from 'jpeg-js';
import jsQR from 'jsqr';
import * as util from '../util';

import cfg from './config';
import request from './request';
import { send } from './help';
import httpServer from './httpServer';
import detectPort from './httpServer/detect-port';

import { isExpire, checkVersion, download } from './download';

const loger = util.loger;

let subprocess;
let isBusying;
let dirTmp;
let sessionOpts;
let fsWatcher;
let killTimer;
let fileCounter = 0;
let batchPrefix = '';

const logger = console;
const fileExt = '.jpg';
const emitter = new EventEmitter();
const mainWindow = remote.getCurrentWindow();
const readdirPromisify = promisify(fs.readdir);
const unlinkPromisify = promisify(fs.unlink);
const existsPromisify = promisify(fs.exists);
const readFilePromisify = promisify(fs.readFile);

/**
 * 扫描数据目录
 */
const getDir = (segs = '', type = 0) => {
  if (!cfg.dataDir) {
    throw '数据保存位置未设置';
  }
  let name = '';
  switch (type) {
    case 0:
      name = '已扫描';
      break;
    case 1:
      name = '已上传';
      break;
    default:
      break;
  }
  return path.join(cfg.dataDir, `试卷采集/${name}/${segs}`);
};

/**
 * 处理消息
 * @param {*} str
 * @param {*} obj
 * @param {*} key
 * @param {*} em
 */
const processMsg = (str, obj, key, em) => {
  if (!str) return;
  obj[key] = `${obj[key] || ''}${str || ''}`;
  let cache = obj[key];
  let idx = cache.indexOf('\r\n\r\n');
  while (idx >= 0) {
    const msg = cache.substring(0, idx);
    cache = obj[key] = cache.substr(idx + 4);
    em.emit(key, msg);
    idx = cache.indexOf('\r\n\r\n');
  }
};

const exeDir = path.join(cfg.dirs.appData, 'scan');
const exePath = path.join(exeDir, 'scan.exe');

const unlink = filePath => {
  return existsPromisify(filePath).then(exists => {
    if (exists) {
      return unlinkPromisify(filePath).then(() => true);
    }
    return false;
  });
};

/**
 * 获取程序
 */
const getExtPath = () => {
  //三个小时监测监测一次
  if (!isExpire(cfg.scanLastCheck, 60 * 24 * 7) && fs.existsSync(exePath)) {
    send('scanStatus', 'prepare', 'end');
    return Promise.resolve(exePath);
  }
  if (isBusying) {
    return Promise.reject('busying');
  }
  isBusying = true;
  send('scanStatus', 'prepare', 'checking');
  const name = 'scan';
  return checkVersion({ name, oldVersion: cfg.scanVersion, urlDownload: cfg.urlDownload })
    .then(data => {
      send('scanStatus', 'prepare', 'data', data);
      // 空数据时返回
      if (!data || !data.files) {
        return;
      }
      // 文件存在
      if (!data.hasUpdate && fs.existsSync(exePath)) {
        cfg.scanLastCheck = new Date();
        cfg.save();
        return;
      }
      const filePs = Object.keys(data.files || []).map(filename => {
        const filePath = path.join(exeDir, filename);
        return download({
          fileUrl: `${cfg.urlDownload}${name}/${data.version}/${filename}`,
          filePath: filePath,
          fileMd5: data.files[filename],
          threads: 5,
        });
      });
      return Promise.all(filePs).then(files => {
        files = files.filter(i => i && i.downStatus);
        if (!files.length) {
          return files;
        }
        send('scanStatus', 'prepare', 'willclose');
        return scanClose().then(() => {
          files.forEach(({ filePath, fileSave }) => {
            if (filePath && fileSave) {
              util.renameSync(fileSave, filePath);
            }
          });

          cfg.scanLastCheck = new Date();
          cfg.scanVersion = data.version;
          cfg.save();
          send('scanStatus', 'prepare', 'updated');
        });
      });
    })
    .catch(error => {
      isBusying = false;
      logger.error(error);
      send('scanStatus', 'prepare', 'error', error);
      throw error;
    })
    .then(() => {
      isBusying = false;
      send('scanStatus', 'prepare', 'end');
      if (fs.existsSync(exePath)) {
        return exePath;
      }
      throw '未找到扫描服务';
    });
};

/**
 * spawn 启动项
 * @param {*} cmd
 * @param {*} args
 * @param {*} options
 * @param {*} ctrls
 */
const getSubprocess = () => {
  return getExtPath().then(exePath => {
    const obj = { info: '', err: '' };
    if (!subprocess) {
      subprocess = spawn(exePath, [mainWindow.getNativeWindowHandle().readUInt32LE(0)]);
      subprocess.stdout.on('data', data => {
        processMsg(data, obj, 'info', emitter);
      });
      subprocess.stderr.on('data', data => {
        logger.warn(data);
        processMsg(data, obj, 'err', emitter);
      });
      subprocess.on('exit', () => {
        free();
        subprocess = undefined;
        emitter.emit('exit');
      });
      subprocess.stdin.setEncoding('utf8');
      subprocess.stdout.setEncoding('utf8');
      subprocess.stderr.setEncoding('utf8');
    }
    emitter.removeAllListeners('info');
    emitter.removeAllListeners('err');
    emitter.removeAllListeners('exit');
  });
};

/**
 * 释放
 */
const free = (dirData, num = 1) => {
  try {
    //停止监控
    if (fsWatcher) {
      fsWatcher.close();
      fsWatcher = undefined;
    }
  } catch (error) {
    logger.error('free', error);
  }
  if (sessionOpts && !util.isEmpty(sessionOpts.timers) && num < 100) {
    setTimeout(() => {
      free(dirData, num + 1);
    }, 50);
    return;
  }
  try {
    if (dirTmp && dirData) {
      if (fs.existsSync(dirTmp)) {
        // 若有文件则移动
        const files = fs.readdirSync(dirTmp);
        if (files.length) {
          loger.debug('fileMove free', files);
          sessionOpts.files = {};
          files.forEach(filename => {
            fileMove(dirData, filename);
          });
        }
        try {
          util.rmdirSync(dirTmp);
        } catch (error) {
          //
        }
      }
    }
  } catch (error) {
    logger.error('free', error);
  }
  dirTmp = undefined;
  isBusying = false;
  send('scanStatus', 'free');
};

/**
 * 关闭
 */
const scanClose = (timeout = 3000) => {
  return new Promise(resolve => {
    if (subprocess) {
      subprocess.once('exit', () => {
        subprocess = undefined;
        send('scanStatus', 'exit', 0);
        clearTimeout(killTimer);
        free();
        resolve(1);
      });

      killTimer = setTimeout(() => {
        free();
        if (subprocess) {
          subprocess.kill();
        }
        send('scanStatus', 'exit', -1);
        resolve(-1);
      }, timeout);
      cmd('close');
    } else {
      free();
      resolve(0);
    }
  }).then(res => {
    return util.killByPath(exePath).then(() => res);
  });
};

/**
 * 发送进程消息
 * @param {*} cmd
 */
const cmd = cmd => {
  if (subprocess) {
    try {
      subprocess.stdin.write(`${cmd}\r\n\r\n`);
    } catch (error) {
      //
    }
  }
};

/**
 * 获取扫描源
 * @returns
 */
const scanSources = async () => {
  return getSubprocess().then(() => {
    return new Promise(resolve => {
      if (isBusying) {
        resolve('busying');
        return;
      }
      isBusying = true;
      emitter.once('info', msg => {
        free();
        resolve(msg.split(/\r\n/m));
      });
      emitter.once('err', msg => {
        free();
        resolve({ error: msg });
      });
      cmd('getds');
    });
  });
};
function PrefixInteger(num, length) {
  return (Array(length).join('0') + num).slice(-Math.max(length, `${num}`.length));
}
const fileMove = (dirData, filename) => {
  try {
    const tempFile = path.join(dirTmp, filename);
    if (!fs.existsSync(tempFile)) {
      loger.debug('fileMove not exists', filename);
      return;
    }
    if (sessionOpts.files[filename]) {
      return;
    }
    sessionOpts.files[filename] = 1;
    if (sessionOpts.reverse) {
      if (!sessionOpts.reverseFlag) {
        fileCounter = fileCounter + 1;
        sessionOpts.reverseFlag = 1;
      } else {
        fileCounter = fileCounter - 2;
        sessionOpts.reverseFlag = 0;
      }
    }
    fileCounter = fileCounter + 1;
    if (fileCounter > 999999) {
      fileCounter = 1;
    }

    const newFile = `${batchPrefix}-${PrefixInteger(fileCounter, 6)}-${new Date().format('ddhhmmss')}${fileExt}`;
    util.renameSync(tempFile, path.join(dirData, newFile));
    if (sessionOpts.reverse) {
      if (!sessionOpts.reverseFlag) {
        fileCounter = fileCounter + 1;
      }
    }
    send('scanStatus', 'scanning', newFile);
  } catch (error) {
    loger.error('fileMove', error);
    send('scanStatus', 'error', error);
  }
};
const startEndMsg = 'start end.';

/**
 * 开始扫描
 */
const scanStart = async (name, { keepon, opts, ds, ...args }, dirInfo) => {
  sessionOpts = opts || {};
  sessionOpts.files = {};
  sessionOpts.timers = {};
  await cfg.getDataDir(dirInfo);
  const dirData = getDir(name);
  await getSubprocess();
  if (isBusying) {
    return 'busying';
  }
  isBusying = true;
  if (!keepon) {
    fileCounter = 0;
    batchPrefix = new Date().format('yyyyMMddhhmmssS');
  }
  dirTmp = fs.mkdtempSync(`${cfg.dirs.temp}\\yx-scan-`);
  util.mkdirSync(dirData);
  fsWatcher = fs.watch(dirTmp, (eventType, filename) => {
    if (eventType === 'change') {
      clearTimeout(sessionOpts.timers[filename]);
      sessionOpts.timers[filename] = setTimeout(() => {
        delete sessionOpts.timers[filename];
        fileMove(dirData, filename);
      }, 100);
    }
  });

  emitter.on('info', msg => {
    if (
      (msg.indexOf('Notify') === 0 && msg.lastIndexOf(startEndMsg) === msg.length - startEndMsg.length) ||
      (msg.indexOf('Error') === 0 && msg.indexOf('loadDS:ERR:') >= 0)
    ) {
      free(dirData);
    }
    send('scanInfo', msg);
  });
  emitter.on('err', msg => {
    send('scanErr', msg);
  });
  emitter.on('exit', () => {
    send('scanStatus', 'exit', 0);
  });
  const obj = {
    _ds: ds,
    _path: dirTmp,
    ...args,
  };
  const params = Object.keys(obj)
    .map(key => `${key}=${obj[key]}`)
    .join('\r\n');
  cmd(`start\r\n${params}`);
  send('scanStatus', 'scanning', params);
  return 'scanning';
};

const readdir = async dir => {
  return fs.existsSync(dir) && readdirPromisify(dir);
};

const scanFiles = async name => {
  return Promise.all([readdir(getDir(name)), readdir(getDir(name, 1))]).then(([scanned, uploaded]) => {
    return { scanned, uploaded };
  });
};

let server;
let serverPort;
const requestListeners = {
  '/file': (uri, res /* , req */) => {
    const { name, file, type } = JSON.parse(decodeURIComponent(uri.query));
    let fullFile = path.join(getDir(name, type), file);
    if (!fs.existsSync(fullFile)) {
      fullFile = path.join(getDir(name, type === 1 ? 0 : 1), file);
    }
    return readFilePromisify(fullFile).then(buffer => {
      res.end(buffer);
    });
  },
};

const runServer = async () => {
  const promise = server
    ? Promise.resolve(serverPort)
    : detectPort({ hostname: '127.0.0.1', port: 0 }).then(port => {
        serverPort = port;
        server = httpServer({ port, requestListeners });
        return port;
      });

  return promise.then(port => `http://127.0.0.1:${port}`);
};

/**
 * 获取单个文件
 * @param {*} name
 * @param {*} param1
 */
const scanFile = async (name, { file, flag, type = 1 } = {}) => {
  if (!flag) {
    return runServer().then(url => {
      const str = JSON.stringify({ name, file, type });
      return `${url}/file?${encodeURIComponent(str)}`;
    });
  }
  const dir = getDir(name, type);
  const fullFile = path.join(dir, file);
  return readFilePromisify(fullFile).then(buffer =>
    flag === 1 ? `data:image/jpeg;base64,${buffer.toString('base64')}` : buffer,
  );
};

/**
 * 文件上传
 * @param {*} name
 * @param {*} param1
 */
const scanUpload = async (name, { isOk = 0, file, url, headers = {}, ...opts } = {}) => {
  const dir = getDir(name);
  const fullFile = path.join(dir, file);
  const body = fs.readFileSync(fullFile);
  if (isOk) {
    return Promise.resolve(util.renameSync(fullFile, path.join(getDir(name, 1), file)));
  }
  return request(url, {
    isEncodeURI: 0,
    body,
    headers: {
      ...headers,
    },
    method: 'post',
    ...opts,
  }).then(({ err, res } = {}) => {
    if (err) {
      throw err;
    }
    const { ok, status, statusText } = res;
    return { ok, status, statusText };
  });
};

/**
 * 删除文件
 * @param {*} name
 * @param {*} param1
 */
const scanDel = async (name, { file, type }) => {
  const dir = getDir(name, type);
  const fullFile = path.join(dir, file);
  return unlink(fullFile);
};

/**
 * 删除目录
 * @param {*} name
 * @param {*} type
 */
const scanDelAll = async (name, type) => {
  const dir = getDir(name, type);
  return util.rmdirSync(dir);
};

/**
 * 打开指定文件夹
 * @param {*} name
 * @param {*} type
 */
const scanOpenDir = async (name, type) => {
  const dir = getDir(name, type);
  if (!fs.existsSync(dir)) {
    await util.mkdirSync(dir);
  }
  shell.openItem(dir);
};

/**
 * 识别文件二维码
 * @param {*} name
 * @param {*} param1
 */
const scanQr = async (name, { file, type = 1 } = {}) => {
  let fullFile = path.join(getDir(name, type), file);
  if (!fs.existsSync(fullFile)) {
    fullFile = path.join(getDir(name, type === 1 ? 0 : 1), file);
  }
  return readFilePromisify(fullFile).then(buffer => {
    const rawImageData = jpeg.decode(buffer);
    const { width, height, data: imgData } = rawImageData;
    const qrInfo = jsQR(imgData, width, height);
    if (qrInfo) {
      const { data, location } = qrInfo;
      return { width, height, qr: { data, location, zoom: 1 } };
    }
    return qrInfo || '';
  });
};

const scanFilesEx = async (name, { onlyFile = true, type } = {}) => {
  if (type === 0) {
    return util.readDirFiles(getDir(name), { onlyFile }).then(scanned => ({ scanned }));
  }
  if (type === 1) {
    return util.readDirFiles(getDir(name, 1), { onlyFile }).then(scanned => ({ scanned }));
  }
  return Promise.all([
    util.readDirFiles(getDir(name), { onlyFile }),
    util.readDirFiles(getDir(name, 1), { onlyFile }),
  ]).then(([scanned, uploaded]) => {
    return { scanned, uploaded };
  });
};

/**
 * 文件移动
 * @param {*} name
 * @param {object.file} file 文件名
 * @param {object.typeSrc} typeSrc 源文件类型
 * @param {object.typeDest} typeDest 目标文件类型
 */
const scanFileMove = async (name, { file, typeSrc = 1, typeDest = 0 } = {}) => {
  util.renameSync(path.join(getDir(name, typeSrc), file), path.join(getDir(name, typeDest), file));
};

/**
 * 文件上传
 * @param {*} name
 * @param {*} param1
 */
const scanUploadEx = async (name, { isOk = 0, file, url, headers = {}, ...opts } = {}) => {
  const dir = getDir(name);
  const fullFile = path.join(dir, file);
  const body = fs.readFileSync(fullFile);
  if (isOk) {
    return Promise.resolve(util.renameSync(fullFile, path.join(getDir(name, 1), file)));
  }
  return request(url, {
    isEncodeURI: 0,
    body,
    headers: {
      ...headers,
      'Content-Length': body.length,
    },
    method: 'post',
    ...opts,
  }).then(({ err, data, res: { status } = {} } = {}) => {
    if (err) {
      throw err;
    }
    return { status, data };
  });
};

export default {
  scanSources,
  scanStart,
  scanClose,
  scanFiles,
  scanFile,
  scanUpload,
  scanDel,
  scanDelAll,
  scanOpenDir,
  scanQr,
  scanFileMove,
  scanFilesEx,
  scanUploadEx,
};
